import qs from "qs";
import * as auth from "service/auth-provider";
import { useAuth } from "../context/auth-context";

const apiURL = process.env.REACT_APP_API_URL;

interface Config extends RequestInit {
  data?: object;
  token?: string;
}

export const http = async (
  endpoint: string,
  { data, token, headers, ...customConfig }: Config = {}
) => {
  const config = {
    method: "GET",
    headers: {
      Authorization: token ? `Bearer ${token}` : "",
      "Content-Type": data ? "application/json" : "",
    },
    ...customConfig,
  };

  // 问：上方method不是已经写死了请求方法为 GET ，为何还要考虑 POST 请求的参数传递？
  // 答：上方将 customConfig解构 在最下方，所以它里面的值理论上会覆盖前面的值
  if (config.method.toUpperCase() === "GET") {
    endpoint += `?${qs.stringify(data)}`;
  } else {
    config.body = JSON.stringify(data || {});
  }

  // 问：axios和fetch的表现差异
  // 答：axios会在返回状态不为2xx的情况下抛出异常
  return window
    .fetch(`${apiURL}/${endpoint}`, config)
    .then(async (response) => {
      // 请求失败
      if (response.status === 401) {
        await auth.logout();
        window.location.reload();
        return Promise.reject({ message: "请重新登录" });
      }
      // 请求成功
      const data = await response.json();
      if (response.ok) {
        return data;
      } else {
        // 问：为何此处需要手动抛出异常？
        // 答：在fetchAPI中服务端返回的异常状态，fetch后面跟的catch不会捕获到异常，也就是fetch不会抛出异常状态，只有在网络连接失败的情况下会抛出异常
        // 所以想要捕获到异常状态，则需要我们手动抛出
        return Promise.reject(data);
      }
    });
};

// 问：为什么要写成一个hook？
// 答：如果你的函数中要使用其他的hook，那么你的函数本身就必须是一个hook
export const useHttp = () => {
  const { user } = useAuth();
  // return ([endpoint, config]: [string, Config]) => http(endpoint, {...config, token: user?.token})
  // 优化
  // Utility Type 的用法：用泛型给他传入一个其他类型，然后utility type对这个类型进行某种操作
  return (...[endpoint, config]: Parameters<typeof http>) =>
    http(endpoint, { ...config, token: user?.token });
};
// JS中的typeof，是在runtime时运行的
// return typeof 1 === '1'

// TS中的typeof，是在静态环境中运行的
// return (...[endpoint, config]: Parameters<typeof http>) =>

// 联合类型
// let myFavoriteNumber: string | number
// myFavoriteNumber = 7
// myFavoriteNumber = '7'
// TS2322: Type '{}' is not assignable to type 'string | number'.
// myFavoriteNumber = {}

// 类型别名在很多情况下可以和interface互换
// interface Person {
//     name: string
// }
// type Person = {name: string}
// const xiaoMing: Person = {name: 'XiaoMing'}

// 类型别名 ---- 将类型抽象出来，在这种情况下interface没办法替换type
// type FavoriteNumber = string | number
// let roseFavoriteNumber: FavoriteNumber = '6'

// interface 也没法实现Utility type

/**
 * Utility type -- 常用类型：Partial，Omit
 * Partial: 使类型变为可选
 * Omit: 删除某一个类型
 */
// type Person = {
//     name: string,
//     age: number
// }
// const xiaoMing: Person = {name: 'xm'}
// const xiaoMing: Partial<Person> = {name: 'xm'}
// const shenMiRen: Omit<Person, 'name'> = {age: 8}
// const shenMiRen: Omit<Person, 'name'|'age'> = {}
